算法——人的天性贪心算法

相应的练习代码:https://github.com/liuxuan320/Algorithm_Exercises


0. 写在前面

说起贪心算法,可能是我们最为熟悉的算法了。正如我标题所说的,贪心算法之所以称之为贪心,就是由于它的核心思想和我们人的天性一模一样。都是选取当前情况下的最优值,但是如何选取一种度量标准,使得我们的贪心能获得全局最优,这是一个值得商榷的问题。下面,我们就开始全面的介绍贪心算法。

1. 贪心算法的定义

贪心算法主要使由于这样一个问题而产生的:

它有N个输入,而它的解就是由这n个输入的某个子集组成,但是要满足某些条件。
这些必须要满足的条件称为约束条件。
把满足约束条件的子集称为该问题的可行解。
度量可行解优劣的函数称为目标函数。
目标的取极值的可行解称为最优解。

以上就是贪心算法的大概描述,那么它的具体实现是如何呢,我们下面给出答案。
贪心算法的步骤:
1. 先选取一种度量标准。
2. 按照这种度量标准对这N个输入进行排序,然后一个一个输入。
3. 如果这个输入不能和当前部分最优解加在一起产生一个可行解,则不把此输入加入到这部分解中。

没错,这就结束了。但是具体的算法应该是这样写的:

//贪心算法的抽象化控制
procedure GREEDY(A,n)
//A(1:n)包含n个输入
    solution<-Φ //将解向量初始化为空
    for i<-1 to n do
        x<-SELECT(A)
        if FEASIBLE(solution,x)
            then solution<-UNION(solution,x)
        endif
    repeat
    return solution
end GREEDY

2. 贪心算法核心——背包问题

1.问题描述

之所以把背包问题单独列出来进行讨论,是因为它的特殊性。由于它是一个经典案例,所以在接下来的很多算法中,都会看到它的身影。
我们这里是普通的背包问题,也就是说可以进行拆分放入的。具体问题描述如下:

已知有N种物品,一个可容纳M重量的背包。每种物品i的重量为 wi ,假定讲物品i的一部分 xi 放入背包就会得到 pixi 的效益,怎样才能使装入物品总重量不超过M,且总收益最大。

2.背包问题的贪心算法
1. 问题描述

很显然这种问题我们经常在故事里听到,很显然,首先我们想到的就是把最贵的东西先装进去,可是最贵的东西一定是总价格最大的么?未必,由于总价格最大的潜在因素就有可能它的体积也非常庞大,因此单价不高。我们生活中嫌弃一个东西贵不贵,都是问单价,比如水果啊什么的,一斤多少钱,这里也是一样。那么我们就可以获得以下抽象化描述:
目标函数:总收益最大
度量标准:
效益值增量最大
单位效益值最大

2. 算法描述

有了上述问题分析后,我们就可以有这样一个算法:

//背包问题的贪心算法
procedure GREEDY-KNAPSACK(P,W,M,X,n)
    //P,W分别按照单位效益由高到低排序,M是背包的容量,X是解向量。
    real P(1:n),W(1:n),X(1:n),M,cu
    integer i,n;
    X←0
    cu←M
    for i←1 to n do
        if W(i)>cu then exit endif
        X(i)←1
        cu←cu-W(i)
    repeat
    if i≤n then X(i)←cu/W(i)
    endif
end GREEDY-KNAPSACK

对于这个算法是不是最优算法,我们可以给出证明,这是最优算法,但是这里由于篇幅有限,不在此说明,具体证明可以自行查阅。

3. 贪心算法若干应用

1. 带有期限的作业排序
1. 问题描述

有限期作业是这样的一个问题:有N个作业需要在1台机器上完成,每个作业均可在单位时间内完成。又假定每个作业i都有一个截止期限 di >0,当且仅当作业i在截止日期前被完成时方可获得 pi >0的效益。

2. 问题求解

通过上述描述,我们可以找到目标函数:总效益最大。而度量标准为:按照收益非增次序排序。这一点和背包问题相类似。
具体的算法如下:

//带有期限和效益的单位时间的作业排序贪心算法
procedure JS(D,J,n,k)
    integer D(0:n),J(0:n),i,k,n,r
    D(0)J(0)←0
    k←1;J(1)←1
    for i←2 to n do
        r←k
        while D(J(r))>D(i) and D(J(r))≠r do
            r←r-1
        repeat
        if D(J(r))≤D(i) and D(i)>r then
            for i←k to r+1 by -1 do
                J(i+1)←J(i)
            repeat
            J(i+1)←i;k←k+1
        endif
    repeat
end JS

这个算法还有优化的空间,大家可以思考一下。

2. 最优归并模式
1. 问题描述

最优归并模式问题描述如下:若两个分别包含n个和m个记录的已排序文件可以在O(m+n)时间内归并到一起而得到一个新排序的文件。现在给出n个文件,求最快归并完成的方案(使用二路归并)

2. 问题求解

看过这个问题,大家很容易想到哈夫曼树,没错,这个和哈夫曼树的想法是已知的,每次都归并尺寸最小的两个文件。具体算法如下:

procedure TREE(L,n)
    for i←1 to n-1 do
        call GETNODE(T)
        LCHILD(T)LEAST(L) //最小的长度
        RCHILD(T)LEAST(L) 
        WEIGHT(T)WEIGHT(LCHILD(T))+WEIGHT(RCHILD(T))
        call INSERT(L,T)
    repeat
    return (LEAST(L))
end TREE

这个代码应该很容易看懂,就是把最小的两个节点合并为新的节点。

3. 最小生成树
1. 问题描述

最小生成树是图论中一个经典的问题,它的解决方法也各不相同,最经典的有两种,一个是Prim算法,一个是Kruskal算法。该问题的具体描述如下:
设G=(V,E)是一个无向连通图。如果G的生成子图T(V,E’)是一棵树,则称T是G的一棵生成树。
而生成树和连通图的关系是:任何一个具有n个节点的连通图都必须至少有n-1条边,而所有具有n-1条边的n结点连通图都是树。

2. Prim算法

prim算法的贪心原则是:选择最短的点加入到解集中
下面我们不介绍具体代码,我们对PRIM算法进行拆解。我么您所需的数据结构有:
COST(n,n) 这是成本的邻接矩阵
T(1:n-1,2)这是解集生成树
NEAR(n) 目标与解集树之间的成本最低的结点。
算法的具体的过程如下:
1. 找到成本最小的边
2. 初始化NEAR,即找出每个点与已生成树之间的成本最低的结点。
3. 在对其余的n-2条边:
对每一条边都计算一下已生成树的成本,并找到最小的一个成本边。
加入至已生成树后,对NEAR进行更新。

3. Kruskal算法

Kruskal算法与Prim算法不同,它的贪心原则是:选择最短的边加入到解集中。其算法如下:

procedure Kruskal
    T←Φ
    while T的边少于n-1条 doE中选取一条最小成本的边(V,W)
        从E中删去(V,Wif(V,W)T中不生成环
            then 将(V,W)加到T
            else 舍弃(V,Wendif
    repeat
end Kruskal

Kruskal的算法的难点在于如何判断一个图是否为连通图,一个图是否存在回路。这个问题,我们会单独进行讲解。

4. 单点源最短路径
1. 问题描述

单源点的最短路径问题也是一个十分经典的算法问题,这个问题如下:从甲地到乙地有多种路径,求最短路径。

2. Dejesk算法

这种问题尤其是在地图路径规划上能够用到,它和多段图的区别在于,它的每个顶点的先后顺序是不固定的,如果用多段图的话,就可以使用动态规划算法了,这里我们使用的Dejesk算法,它的贪心规则为每次选择与 V0 最短的路径。
关于Dejesk算法,我们对其核心思想进行讲解,不写具体代码,具体代码可以在我的Github上找到。
我们所需的数据结构有COST(n,n),这是成本邻接矩阵,DIST(n),这是从V(出发点)到i点的最短距离,到自己的点为0,S(n)这是解集,里面只存0(未纳入)和1(已纳入)。
具体步骤如下:
1. 首先初始化解集S,全置为0,初始化DIST(n)为COST(V,i)
2. 然后把开始结点V纳入解集中S(V)←1,DIST(V)←0
3. 对于剩下的n-1条路径
1)找到DIST(n)中最小的DIST(u)
2) 把u纳入到解集中
3)修改DIST(n)中的所有S(i)=0的点,修改方法为DIST(w)←min(DIST(w),DIST(u)+cost(u,w))
这样,我们的Dejesk算法就介绍完毕了。这次贪心算法比较简单,大家可以自己实现一下。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI让世界更懂你

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值